/**
 * Project Name:myutils
 * Date:2016年9月10日
 * Copyright (c) 2016, jingma All Rights Reserved.
 */

package cn.benma666.sjsj.web;

import cn.benma666.constants.UtilConstInstance;
import cn.benma666.dict.Cllx;
import cn.benma666.domain.SysSjglFile;
import cn.benma666.domain.SysSjglSjdx;
import cn.benma666.domain.SysSjglSjzd;
import cn.benma666.exception.ExcelReadException;
import cn.benma666.exception.MyException;
import cn.benma666.exception.SuccessException;
import cn.benma666.iframe.MyParams;
import cn.benma666.iframe.Result;
import cn.benma666.json.JsonUtil;
import cn.benma666.myutils.StringUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.TypeUtils;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 数据对象Excel处理工具 <br/>
 * date: 2016年9月10日 <br/>
 *
 * @author jingma
 * @version 0.1
 */
@Setter
@Getter
public class SjdxExcelReader extends AnalysisEventListener<Map<Integer, String>> implements UtilConstInstance {
    /**
     * 日志
     */
    protected final Logger log = LoggerFactory.getLogger(this.getClass());
    /**
     * 表单值
     */
    private JSONObject formObj;
    /**
     * 开始行,0、1、2....
     */
    protected int startRow = 1;
    /**
     * 当前行
     */
    protected int currRow = 0;
    /**
     * 文件数据对象
     */
    protected SysSjglFile fileObj;
    /**
     * 数据对象相关信息
     */
    protected MyParams myParams;
    /**
     * 当前对应的数据对象
     */
    protected SysSjglSjdx sjdx;
    /**
     * 字段对象列表
     */
    protected Map<String, SysSjglSjzd> fields;
    /**
     * 结果
     */
    protected JSONArray result = new JSONArray();
    /**
     * 数据验证
     */
    private boolean sjyz;

    /**
     * @param sjdx     数据对象
     * @param myParams 相关参数
     * @param fileObj  对应的文件对象
     */
    public SjdxExcelReader(SysSjglSjdx sjdx, MyParams myParams, SysSjglFile fileObj) {
        this.sjdx = sjdx;
        //克隆一个参数对象，避免被修改
        this.myParams = JsonUtil.clone(myParams);
        this.fileObj = fileObj;
        //合并基础信息
        formObj = myParams.getJSONObject(KEY_YOBJ);
        if(formObj==null){
            formObj = new JSONObject();
        }
        this.sjyz = TypeUtils.castToBoolean(StringUtil.valByDef(myParams.sys().getSjyz(),1));
    }

    /**
     * 处理Excel <br/>
     *
     * @return 处理结果
     * @author jingma
     */
    public Result disposeExcel() {
        try {
            //自动根据文件类型解析
            ExcelTypeEnum type = ExcelTypeEnum.valueOf(fileObj.getWjlx().toUpperCase());
            InputStream in;
            if (type.equals(ExcelTypeEnum.CSV)) {
                //CSV文件进行编码转换
                in = new ByteArrayInputStream(new String(fileObj.getFileBytes(), "GBK").getBytes());
            } else {
                in = fileObj.getInputStream();
            }
            EasyExcel.read(in, this).ignoreEmptyRow(true)
                    .autoTrim(true).excelType(type).sheet().headRowNumber(startRow).doRead();
        } catch (SuccessException e){
            log.debug("读取完成："+result.size());
        } catch (MyException e) {
            throw e;
        } catch (IllegalArgumentException e){
            JSONObject sjdxkz = sjdx.tailsToJSONObject();
            //easy不支持的格式
            if ("txt".equals(fileObj.getWjlx())) {//文本场景
                //分隔符
                String fgf = StringUtil.valByDef(sjdxkz.getString("fgf"), ",");
                //文本限定符
                String wbxdf = StringUtil.valByDef(sjdxkz.getString("wbxdf"),"\"");
                //编码方式
                String bmfs = StringUtil.valByDef(sjdxkz.getString("bmfs"), "utf8");
                String context;
                try {
                    context = new String(fileObj.getFileBytes(),bmfs).trim();
                } catch (UnsupportedEncodingException ex) {
                    throw new MyException("文件编码异常："+bmfs,ex);
                }
                //去除首尾
                context = context.substring(wbxdf.length(),context.length()-(wbxdf+"\n").length());
                int clo;
                Map<Integer, String> val = new HashMap<>();
                for (String row:context.split(wbxdf+"\n"+wbxdf)){
                    clo = 0;
                    for (String field:row.split(wbxdf + fgf + wbxdf)){
                        val.put(clo,field);
                        clo++;
                    }
                    if(currRow==0){
                        invokeHeadMap(val,null);
                    }else {
                        invoke(val,null);
                    }
                }
            } else {
                throw new MyException("暂不支持的文件类型" + fileObj.getWjlx());
            }
        } catch (Exception e) {
            throw new MyException("文件处理失败，可能是文件损坏，请打开文件，重新保存后再尝试上传",e);
        }
        if(sjyz){
            //批量数据转换验证
            myParams.sys().setCllx(Cllx.plsjzhyz.name());
            myParams.sys().setEditTableData(getResult());
            return LjqManager.data(myParams);
        }
        return Result.success(fileObj.getWjm()+"数据读取完成",getResult());
    }

    /**
     * 读取表头信息
     *
     * @param headMap 表头数据
     * @param context 上下文
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        currRow++;
        log.debug("文件头信息：" + headMap);
        JSONObject sjdxkz = sjdx.tailsToJSONObject();
        //文件严格校验
        boolean ygjy = sjdxkz.getBooleanValue("ygjy");
        //文件允许无效字段
        boolean yxwxzd = sjdxkz.getBooleanValue("yxwxzd");
        //设置字段信息
        this.fields = new LinkedHashMap<>();
        Map<String, SysSjglSjzd> fieldMap = (Map<String, SysSjglSjzd>) myParams
                .get(KEY_FIELDS);
        //字段名称转为key
        Map<String,Integer> hMap = new HashMap<>();
        headMap.forEach((key, val) -> hMap.put(val,key));
        for (SysSjglSjzd field : fieldMap.values()) {
            SysSjglSjzd f = (SysSjglSjzd) field.clone();
            String mc = f.getZdmc();
            if(!hMap.containsKey(mc)){
                mc = f.getZdmc() + "[" + f.getZddm() + "]";
            }
            if (hMap.containsKey(mc)){
                //设置该字段在文件中的列
                f.setPx(hMap.get(mc));
                //移除已处理的列
                hMap.remove(mc);
                fields.put(f.getZddm(), f);
            }else if (TypeUtils.castToBoolean(field.getMbzs())&&ygjy) {
                //支持带字段代码或不带字段代码，模板展示且要求严格校验
                throw new ExcelReadException("缺少必填字段：" + mc
                        + "，请重新下载模板文件，不要进行结构调整，进行数据填充即可");
            }
        }
        if(hMap.size()>0){
            if(!yxwxzd){
                throw new ExcelReadException("文件存在无效字段："+StringUtil.join(hMap.keySet(),"，"));
            }
            if(fields.size()<2){
                throw new ExcelReadException("文件有效字段少于两个："+StringUtil.join(fields.keySet(),"，"));
            }
        }
        if(fields.size()==0){
            throw new MyException("没有可以处理的字段："+sjdx.getDxmc());
        }else {
            log.debug("处理的字段：{}",fields.keySet());
        }
    }

    /**
     * 处理具体每一行数据 <br/>
     *
     * @param rowMap 具体数据行
     * @author jingma
     */
    @Override
    public void invoke(Map<Integer, String> rowMap, AnalysisContext context)
            throws RuntimeException {
        currRow++;
        log.trace("{}行数据：{}",currRow, JSON.toJSONString(rowMap));
        //开始包装数据
        JSONObject yobj = formObj.clone();
        String val;
        for (Entry<String, SysSjglSjzd> e : fields.entrySet()) {
            val = rowMap.get(e.getValue().getPx());
            if(StringUtil.isNotBlank(val)){
                yobj.put(e.getKey(), val);
            }
        }
        //设置文件名
        yobj.put("myWjm",fileObj.getWjm());
        //设置文件id
        yobj.put("mySswj",fileObj.getId());
        result.add(yobj);
    }

    /**
     * 数据读取完成操作
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
    }
}
